/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import { ASTElement, ASTNode, compile } from 'vue-template-compiler'
import * as clipboard from 'clipboard-polyfill/text'
import debounce from 'lodash.debounce'
import shortid from 'shortid'
import File from '@/core/file-model/File'
import PageConfig from '@/core/model/PageConfig'
import WidgetConfig from '@/core/model/WidgetConfig'
import ApplicationEditor from '@/components/ApplicationEditor.vue'
import WidgetDefinition from '@/core/model/WidgetDefinition'

const getValue = (rawAttrsMap: any, key: string) => {
  return (rawAttrsMap[key] || rawAttrsMap[':' + key])?.value
}

@Component
export default class FileHandler extends Vue {
  /** 文件对象 */
  @Prop()
  public file!: File

  /** appEditor 对象 */
  @Prop()
  public appEditor!: ApplicationEditor

  /** pageConfig */
  public pageConfig: PageConfig | null = null

  /** pageConfig 对应的组件树 */
  public pageContainerASTElement: ASTElement | null = null

  /** 选中的组件的Ids */
  public selectedWidgetConfigIds: string[] = []

  /** 当前处于鼠标悬浮状态的组件的ID */
  public hoverWidgetConfigId: string | null = null

  /** 文件名 */
  public get fileName() {
    return this.file.name
  }

  /** 文件内容 */
  public get content() {
    return this.file.content
  }

  /** 文件内容 */
  public set content(content: string) {
    this.file.content = content
  }

  /** 所有的组件设置 */
  public get widgetConfigs() {
    const result: WidgetConfig[] = []
    const traverse = (widgetConfigs: WidgetConfig[]) => {
      if (!widgetConfigs.length) {
        return
      }
      for (const widgetConfig of widgetConfigs) {
        result.push(widgetConfig)
        traverse(widgetConfig?.children || [])
      }
    }
    traverse(this.pageConfig?.widgetConfigs || [])
    return result
  }

  /** 选中的组件设置 */
  public get selectedWidgetConfigs() {
    return this.widgetConfigs.filter((d) => this.selectedWidgetConfigIds.indexOf(d.id) !== -1) || []
  }

  /** 单选的图层设置 */
  public get singleSelectedWidgetConfig() {
    return this.selectedWidgetConfigs.length === 1 ? this.selectedWidgetConfigs[0] : null
  }

  /** 多选的组件设置 */
  public get multipleSelectedWidgetConfig() {
    return this.selectedWidgetConfigs.length > 1 ? this.selectedWidgetConfigs : []
  }

  /** 选中的组件设置的先祖 */
  public get selectedAncestors() {
    const ancestors: WidgetConfig[] = []
    const traverse = (widgetConfig: WidgetConfig) => {
      const parentWidget = widgetConfig.parentWidget
      if (parentWidget) {
        ancestors.push(parentWidget)
        traverse(parentWidget)
      }
    }
    // this.selectedWidgetConfigs.forEach(traverse)

    if (this.selectedWidgetConfigs.length) {
      this.selectedWidgetConfigs.forEach(traverse)
    }

    return ancestors
  }

  /** 选中的组件设置的先祖的ID */
  public get selectedAncestorIds() {
    return this.selectedAncestors.map(d => d.id)
  }

  /** 处于选中状态 */
  public get hasSelected() {
    return this.selectedWidgetConfigs.length > 0
  }

  /** 处于多选状态 */
  public get isMultipleSelected() {
    return this.multipleSelectedWidgetConfig.length > 0
  }

  /** 选择一个组件 */
  public selectWidgetConfig (widgetConfigId: string, multiSelect = false) {
    const first = this.selectedWidgetConfigs[0]
    const widgetConfig = this.getWidgetConfigById(widgetConfigId)

    if (!widgetConfig) {
      return
    }

    if (multiSelect) {
      if (first?.parentWidget !== widgetConfig.parentWidget) {
        this.clearSelected()
      }
      const index = this.selectedWidgetConfigIds.indexOf(widgetConfigId)
      if (index > -1) {
        this.selectedWidgetConfigIds.splice(index, 1)
      } else {
        this.selectedWidgetConfigIds.push(widgetConfigId)
      }
    } else {
      this.selectedWidgetConfigIds = [widgetConfigId]
    }
  }

  /** 根据ID获取WidgetConfig */
  public getWidgetConfigById(widgetConfigId: string) {
    return this.widgetConfigs.find((d) => d.id === widgetConfigId)
  }

  /** 编组选中的组件 */
  public groupSelectedWidgetConfigs() {
    const selectedWidgetConfigs = this.selectedWidgetConfigs
    const first = selectedWidgetConfigs[0]
    const parentWidget = first.parentWidget
    const widgetConfigs = this.pageConfig?.widgetConfigs || []

    const x0 = Math.min(...selectedWidgetConfigs.map((d) => d.base.x))
    const y0 = Math.min(...selectedWidgetConfigs.map((d) => d.base.y))
    const x1 = Math.max(...selectedWidgetConfigs.map((d) => d.base.x + d.base.w))
    const y1 = Math.max(...selectedWidgetConfigs.map((d) => d.base.y + d.base.h))
    const children = []

    const index = parentWidget?.children.indexOf(first) || widgetConfigs.indexOf(first)
  
    const group = new WidgetConfig()
    for (const selectedWidgetConfig of selectedWidgetConfigs) {
      this.deleteWidgetConfigById(selectedWidgetConfig.id, false)
      selectedWidgetConfig.parentWidget = group
      selectedWidgetConfig.base.x -= x0
      selectedWidgetConfig.base.y -= y0
      children.push(selectedWidgetConfig)
    }

    group.name = '组'
    group.base.x = x0
    group.base.y = y0
    group.base.w = x1 - x0
    group.base.h = y1 - y0
    group.children = children
    group._widgetTag = 'WidgetGroup'

    if (parentWidget) {
      group.parentWidget = parentWidget
      parentWidget.children.splice(index,0, group)
    } else if (this.pageConfig) {
      widgetConfigs.splice(index, 0, group)
    }
  }

  /** 解组选中的组件 */
  public ungroupSelectedWidgetConfigs() {
    // 取消组
    const singleSelectedWidgetConfig = this.singleSelectedWidgetConfig

    if (
      singleSelectedWidgetConfig &&
      singleSelectedWidgetConfig._widgetTag === 'WidgetGroup'
    ) {
      const parentWidget = singleSelectedWidgetConfig.parentWidget
      const children = singleSelectedWidgetConfig.children
      for (const child of children) {
        child.base.x += singleSelectedWidgetConfig.base.x
        child.base.y += singleSelectedWidgetConfig.base.y
        child.parentWidget = parentWidget || null
      }

      if (parentWidget) {
        const index = parentWidget.children.indexOf(singleSelectedWidgetConfig)
        parentWidget.children.splice(index, 1, ...children)
      } else if (this.pageConfig) {
        const index = this.pageConfig.widgetConfigs.indexOf(singleSelectedWidgetConfig)
        this.pageConfig.widgetConfigs.splice(index, 1, ...children)
      }
    }
    
  }

  /** 删除一个图层（组件） */
  public deleteWidgetConfigById (widgetConfigId: string, deleteEmptyParentGroup = true) {
    const widgetConfig = this.getWidgetConfigById(widgetConfigId)
    const parentWidgetConfig = widgetConfig?.parentWidget
    const list = parentWidgetConfig?.children || this.pageConfig?.widgetConfigs || []
    const index = list.findIndex((d) => d.id === widgetConfigId)

    if (index > -1) {
      list.splice(index, 1)
    }

    if (
      deleteEmptyParentGroup &&
      list.length === 0 &&
      parentWidgetConfig &&
      parentWidgetConfig._widgetTag === 'WidgetGroup'
    ) {
      this.deleteWidgetConfigById(parentWidgetConfig.id)
    }
  }

  /** 删除所选中的图层 */
  public async deleteAllSelectedWidgetConfig () {
    for (const selectedId of this.selectedWidgetConfigIds) {
      if (selectedId) {
        this.deleteWidgetConfigById(selectedId)
      }
    }
  }

  /** 清除选中 */
  public clearSelected () {
    this.selectedWidgetConfigIds = []
  }

  /** 判断一个组件是否被选中 */
  public isSelectedWidgetConfigId (widgetConfigId: string) {
    return this.selectedWidgetConfigIds.indexOf(widgetConfigId) > -1
  }

  /** 判断一个组件是否是被选中组件的祖先 */
  public isSelectedAncestorId (widgetConfigId: string) {
    return this.selectedAncestorIds.indexOf(widgetConfigId) > -1
  }

  /** 拷贝组件至剪切板 */
  public copySelectedWidgetConfig() {
    let code = ''
    this.selectedWidgetConfigs.forEach(d => {
      code += d._code
    })

    clipboard.writeText(code)
  }

  /** 从剪切板粘贴 */
  public async pasteWidgetConfig(x: number, y: number) {
    const clipboardText = await clipboard.readText()
    const code = '<code-root>' + clipboardText + '</code-root>'

    const result = compile(code, {
      outputSourceRange: true
    })
    const astNodes = result.ast?.children

    const widgetElements = astNodes?.filter(d => this.isWidgetTagAstNode(d)) as ASTElement[]
    const widgetConfigs = widgetElements.map(d => this.astElementToWidgetConfig(d))
    const traverseWidgetConfigs: WidgetConfig[] = []
    widgetConfigs.forEach((widgetConfig: WidgetConfig) => {
      traverseWidgetConfigs.push(...this.traverseWidgetConfig(widgetConfig))
    })
    traverseWidgetConfigs.forEach((widgetConfig) => {
      widgetConfig.id = shortid.generate()
    })

    const minX = Math.min(...widgetConfigs.map(d => d.base.x))
    const minY = Math.min(...widgetConfigs.map(d => d.base.y))

    widgetConfigs.forEach(d => {
      d.base.x -= minX
      d.base.y -= minY
      d.base.x += x
      d.base.y += y
    })

    widgetConfigs.forEach(widgetConfig => {
      this.pageConfig?.widgetConfigs.push(widgetConfig)
    })

    this.selectedWidgetConfigIds = widgetConfigs.map(d => d.id)
  }

  public addWidget(widgetDefinition: WidgetDefinition, x?: number, y?: number) {
    const widgetConfig = new WidgetConfig(widgetDefinition)
    widgetConfig.base.x = x || 0
    widgetConfig.base.y = y || 0
    this.pageConfig?.widgetConfigs.push(widgetConfig)
  }

  public updatePageConfig() {
    this.pageConfig = null
    let vueAst = null
    if (this.fileName.endsWith('.vue')) {
      const result = compile(this.content, {
        outputSourceRange: true
      })
      vueAst = result.ast
    }

    if (!vueAst) {
      return
    }

    const astElements: ASTElement[] = []
    const traverse = (el: ASTElement ) => {
      if(el.tag) {
        astElements.push(el)
      }
      for (const child of el.children) {
        if( child.type === 1) {
          traverse(child)
        }
      }
    }
    traverse(vueAst)

    const pageContainerASTElement = astElements.find((d) => d.tag === 'PageContainer')
    this.pageContainerASTElement = pageContainerASTElement || null
    if (!pageContainerASTElement) {
      return
    }
    
    const rawAttrsMap = (pageContainerASTElement as any).rawAttrsMap || {}

    const pageConfig = new PageConfig()
  
    pageConfig.width = Number(getValue(rawAttrsMap, 'width')) || pageConfig.width
    pageConfig.height = Number(getValue(rawAttrsMap, 'height')) || pageConfig.height
    pageConfig.bgColor = getValue(rawAttrsMap, 'bgColor') || pageConfig.bgColor

    const traverseAST = (astNodes: ASTNode[], widgetConfigs: WidgetConfig[], parentWidget?: WidgetConfig) => {
      const widgetASTElements = this.getWidgetASTElements(astNodes)
      for (let index = 0; index < widgetASTElements.length; index++) {
        const widgetASTElement = widgetASTElements[index]
        const rawAttrsMap = (widgetASTElement as any).rawAttrsMap
        const start = (widgetASTElement as any).start
        const end = (widgetASTElement as any).end
        const tag = widgetASTElement.tag
        
        const widgetDefinition = this.getWidgetDefinitionBytag(tag)
        const widgetConfig = new WidgetConfig(widgetDefinition)
        widgetConfig.parentWidget = parentWidget || null

        this.setWidgetConfig(widgetConfig, rawAttrsMap)
        widgetConfig.id = getValue(rawAttrsMap, 'ref') || widgetConfig.id
        widgetConfig._widgetTag = tag
        widgetConfig._code = this.content.slice(start, end) || ''


        if (widgetDefinition?.autoSize) {
          widgetConfig.base.w = 0
          widgetConfig.base.h = 0
        }

        if (widgetASTElement.children.length) {
          traverseAST(widgetASTElement.children, widgetConfig.children, widgetConfig)
        }

        widgetConfigs.push(widgetConfig)
      }
    }

    traverseAST(pageContainerASTElement.children, pageConfig.widgetConfigs)

    this.pageConfig = pageConfig
  }

  public updateCode () {
    if (!this.pageConfig) {
      return
    }
    const pageContainerASTElement = this.pageContainerASTElement
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const start = (pageContainerASTElement as any).start || -1
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const end = (pageContainerASTElement as any).end || -1

    const indentStart = this.getIndent(start)
    const lineStart = (n: number) => `\n${this.indentText(indentStart + n)}`
    const stratStr = this.content.slice(0, start)
    const endStr = this.content.slice(end)
    
    let pageContainerTagStart = ''
    let pageContainerAttr = ''

    pageContainerAttr += `${lineStart(2)}:width="${this.pageConfig.width}"`
    pageContainerAttr += `${lineStart(2)}:height="${this.pageConfig.height}"`
    pageContainerAttr += `${lineStart(2)}bgColor="${this.pageConfig.bgColor}"`
    
    pageContainerTagStart = `<PageContainer${pageContainerAttr}${lineStart(0)}>`
    const pageContainerTagEnd = `${lineStart(0)}</PageContainer>`

    let widgets = ''

    const traverse = (widgetConfigs: WidgetConfig[], indent: number) => {
      for (const widgetConfig of widgetConfigs) {
        let widgetTagStart = `${lineStart(indent * 2 + 2)}<${widgetConfig._widgetTag}`
        widgetTagStart += this.getAtttrs(widgetConfig, indent, indentStart)
        widgetTagStart += `${lineStart(indent * 2 + 2)}>`
        widgets += widgetTagStart

        if (widgetConfig.children.length) {
          traverse(widgetConfig.children, indent + 1)
          widgets += `${lineStart(indent * 2 + 2)}</${widgetConfig._widgetTag}>`
        } else {
          widgets += `</${widgetConfig._widgetTag}>`
        }
      }
    }

    traverse(this.pageConfig.widgetConfigs, 0)

    const newStr = pageContainerTagStart + widgets + pageContainerTagEnd
    this.content = `${stratStr}${newStr}${endStr}`
  }

  @Watch('content')
  private handleContentChange() {
    this.updatePageConfig()
  }

  @Watch('pageConfig', { deep: true })
  private handlePageConfigChange(newValue: PageConfig, oldVlaue: PageConfig) {
    if(newValue !== oldVlaue) {
      return
    }

    this.updateContentDebounce()
  }

  private updateContentDebounce = debounce(() => {
    this.updateCode()
  }, 200)

  private isWidgetTagAstNode(astNode: ASTNode) {
    if(astNode.type === 1) {
      return this.isWidgetTag((astNode as ASTElement).tag)
    }
    return false
  }

  private isWidgetTag(arg: string) {
    const isWidgetTag: (arg: string) => boolean = (this.appEditor as any).isWidgetTag
    return isWidgetTag(arg)
  }

  private getWidgetDefinitionBytag(tag: string) {
    const getWidgetDefinitionBytag: (tag: string) => WidgetDefinition | undefined = (this.appEditor as any).getWidgetDefinitionBytag
    return getWidgetDefinitionBytag(tag)
  }

  private getWidgetASTElements (astNodes: ASTNode[]) {
    const isWidgetTag = this.isWidgetTag
    const widgetASTElements = astNodes.filter((d) => isWidgetTag((d as ASTElement).tag)) as ASTElement[]
    return widgetASTElements
  }

  private traverseWidgetConfig(widgetConfig: WidgetConfig) {
    let result: WidgetConfig[] = []
    result.push(widgetConfig)
    widgetConfig.children.forEach(d => {
      result = result.concat(this.traverseWidgetConfig(d))
    })
    return result
  }

  private astElementToWidgetConfig(widgetASTElement: ASTElement, parentWidget?: WidgetConfig) {
    if (this.isWidgetTag(widgetASTElement.tag)) {
      const widgetConfig = new WidgetConfig()
      widgetConfig.parentWidget = parentWidget || null
      const rawAttrsMap = (widgetASTElement as any).rawAttrsMap
      const start = (widgetASTElement as any).start
      const end = (widgetASTElement as any).end
      const tag = widgetASTElement.tag

      this.setWidgetConfig(widgetConfig, rawAttrsMap)
      widgetConfig.id = getValue(rawAttrsMap, 'ref') || widgetConfig.id
      widgetConfig._widgetTag = tag
      widgetConfig._code = this.content.slice(start, end) || ''

      const children = (widgetASTElement.children as ASTElement[]).filter(d => this.isWidgetTag(d.tag))

      widgetConfig.children = children.map((d) => {
        return this.astElementToWidgetConfig(d)
      })

      return widgetConfig
    } else {
      throw new Error(`${widgetASTElement.tag} 不是有效的组件TAG`)
    }
  }

  private getIndent(index: number) {
    let indent = 0
    const next = () => this.content.charAt(--index) === ' '
    while (next()) {
      indent++
    }
    return indent
  }

  private indentText(indent: number) {
    return Array(indent).fill(' ').join('')
  }

  private setWidgetConfig (widgetConfig: WidgetConfig, rawAttrsMap: any) {
    const defaultWidgetConfig = new WidgetConfig(widgetConfig.widgetDefinition)
    const filter = (d: string) => !(d.startsWith('_') || d === 'id' || d === 'base' || d === 'children' || d === 'parentWidget' || d === 'widgetDefinition')
    const keys = Object.keys(defaultWidgetConfig).filter(filter)
    const baseKeys = Object.keys(defaultWidgetConfig.base).filter(filter)
  
    for (const key of keys) {
      const defaultValue = (defaultWidgetConfig as any) [key]
      const value = getValue(rawAttrsMap, key)
      if (typeof defaultValue === 'string') {
        (widgetConfig as any)[key] = value ? String(value) : defaultValue
      } else if (typeof defaultValue === 'number') {
        (widgetConfig as any)[key] = value ? Number(value) : defaultValue
      }
    }
  
    for (const key of baseKeys) {
      const defaultValue = (defaultWidgetConfig.base as any) [key]
      const value = getValue(rawAttrsMap, key)
      if (typeof defaultValue === 'string') {
        (widgetConfig.base as any)[key] = value ? String(value) : defaultValue
      } else if (typeof defaultValue === 'number') {
        (widgetConfig.base as any)[key] = value ? Number(value) : defaultValue
      }
    }
  
    widgetConfig.id = getValue(rawAttrsMap, 'ref') || widgetConfig.id
  }

  private getAtttrs (widgetConfig: WidgetConfig, indent: number, indentStart: number) {
    let result = ''
    const widgetDefinition = this.getWidgetDefinitionBytag(widgetConfig._widgetTag)
    const defaultWidgetConfig = new WidgetConfig(widgetDefinition)
    const filter = (d: string) => !(d.startsWith('_') || d === 'id' || d === 'base' || d === 'children' || d === 'parentWidget' || d === 'widgetDefinition')
    const keys = Object.keys(defaultWidgetConfig).filter(filter)
    const baseKeys = Object.keys(defaultWidgetConfig.base).filter(filter)
    const lineStart = (n: number) => `\n${this.indentText(indentStart + n)}`

    result += `${lineStart(indent * 2 + 4)}ref="${widgetConfig.id}"`

    for (const key of keys) {
      if ((widgetConfig as any)[key] !== (defaultWidgetConfig as any)[key]) {
        if (isNaN((widgetConfig as any)[key])) {
          result += `${lineStart(indent * 2 + 4)}${key}="${(widgetConfig as any)[key]}"`
        } else {
          result += `${lineStart(indent * 2 + 4)}:${key}="${(widgetConfig as any)[key]}"`
        }
      }
    }

    for (const key of baseKeys) {
      if (widgetDefinition?.autoSize && (key === 'w' || key === 'h')) {
        continue
      }
      if ((widgetConfig.base as any)[key] !== (defaultWidgetConfig.base as any)[key]) {
        result += `${lineStart(indent * 2 + 4)}:${key}="${(widgetConfig.base as any)[key]}"`
      }
    }

    return result
  }

  private created() {
    this.updatePageConfig()
  }
}
